{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# @函数装饰器及用法"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 工作原理"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "funA作为装饰器函数\n",
    "```\n",
    "def funA(fn):\n",
    "    #...\n",
    "    fn()\n",
    "    #...\n",
    "    return '...'\n",
    "\n",
    "@funA\n",
    "def funB():\n",
    "    #...\n",
    "    pass\n",
    "```"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "等同于下面的程序"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "funA作为装饰器函数\n",
    "```\n",
    "def funA(fn):\n",
    "    #...\n",
    "    fn()\n",
    "    #...\n",
    "    return '...'\n",
    "def funB():\n",
    "    #...\n",
    "    pass\n",
    "funB = funA(funB)\n",
    "```"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "before invoke...\n",
      "function invoke\n",
      "after invoke...\n",
      "invoke response\n"
     ]
    }
   ],
   "source": [
    "\n",
    "def funA(fn):\n",
    "    print('before invoke...')\n",
    "    fn()\n",
    "    print('after invoke...')\n",
    "    return 'invoke response'\n",
    "\n",
    "@funA\n",
    "def funB():\n",
    "    print('function invoke')\n",
    "\n",
    "print(funB)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "被“＠函数”修饰的函数不再是原来的函数，而是被替换成一个新的东西（取决于装饰器的返回值），即如果装饰器函数的返回值为普通变量，那么被修饰的函数名就变成了变量名；同样，如果装饰器返回的是一个函数的名称，那么被修饰的函数名依然表示一个函数。\n",
    "> 实际上，所谓函数装饰器，就是通过装饰器函数，在不修改原函数的前提下，来对函数的功能进行合理的扩充。"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 带参数的函数装饰器\n",
    "\n",
    "在函数装饰器中嵌套一个函数，该函数带有的参数个数和被装饰器修饰的函数相同。"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "装饰器函数执行:  pangyazhou\n"
     ]
    }
   ],
   "source": [
    "def funA(fn):\n",
    "    # 定义一个嵌套函数\n",
    "    def say(arc):\n",
    "        print('装饰器函数执行: ', arc)\n",
    "    return say\n",
    "\n",
    "@funA\n",
    "def funB(arc):\n",
    "    print('原始函数: ', arc)\n",
    "\n",
    "funB('pangyazhou')"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "有多个（≥ 2）函数被同一个装饰器函数修饰，这些函数带有的参数个数并不相等\n",
    "解决方式是用 *args 和 **kwargs 作为装饰器内部嵌套函数的参数，*args 和 **kwargs 表示接受任意数量和类型的参数。"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "前置处理器\n",
      "原始函数:  nankai\n",
      "后置处理器\n",
      "前置处理器\n",
      "pangyazhou nankai\n",
      "后置处理器\n"
     ]
    }
   ],
   "source": [
    "def funA(fn):\n",
    "    # 定义一个嵌套函数\n",
    "    def say(*args, **kwargs):\n",
    "        print('前置处理器')\n",
    "        fn(*args, **kwargs)\n",
    "        print(\"后置处理器\")\n",
    "    return say\n",
    "\n",
    "@funA\n",
    "def funB(arc):\n",
    "    print('原始函数: ', arc)\n",
    "\n",
    "@funA\n",
    "def funC(name, arc):\n",
    "    print(name, arc)\n",
    "\n",
    "funB('nankai')\n",
    "funC('pangyazhou', 'nankai')"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 函数装饰器可以嵌套\n",
    "```python\n",
    "@funA\n",
    "@funB\n",
    "@funC\n",
    "def fun():\n",
    "    #...\n",
    "```\n",
    "上面程序的执行顺序是里到外，所以它等效于下面这行代码：\n",
    "```\n",
    "fun = funA(funB(funC(fun)))\n",
    "```"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 闭包\n",
    "在 Python 中允许在一个方法中嵌套另一个方法，这种特殊的机制就叫做「闭包」，这个内部方法可以保留外部方法的作用域，尽管外部方法不是全局的，内部方法也可以访问到外部方法的参数和变量。"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hello,world\n",
      "duration time: 1s\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "\n",
    "\n",
    "def timeit(func):\n",
    "    start = time.time()\n",
    "    func()\n",
    "    end = time.time()\n",
    "    print('duration time: %ds' % int(end - start))\n",
    "\n",
    "def hello():\n",
    "    time.sleep(1)\n",
    "    print('hello,world')\n",
    "\n",
    "timeit(hello)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hello,world\n",
      "duration time: 1s\n"
     ]
    }
   ],
   "source": [
    "def timeit(func):\n",
    "    def inner():\n",
    "        start = time.time()\n",
    "        func()\n",
    "        end = time.time()\n",
    "        print('duration time: %ds' % int(end - start))\n",
    "    return inner\n",
    "def hello():\n",
    "    time.sleep(1)\n",
    "    print('hello,world')\n",
    "\n",
    "hello = timeit(hello)\n",
    "hello()"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 装饰器"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hello,world\n",
      "duration time: 1s\n",
      "inner\n"
     ]
    }
   ],
   "source": [
    "def timeit(func):\n",
    "    def inner():\n",
    "        start = time.time()\n",
    "        func()\n",
    "        end = time.time()\n",
    "        print('duration time: %ds' % int(end - start))\n",
    "    return inner\n",
    "\n",
    "@timeit\n",
    "def hello():\n",
    "    time.sleep(1)\n",
    "    print('hello,world')\n",
    "\n",
    "# @timeit <==> hello=timeit(hello)\n",
    "hello()\n",
    "print(hello.__name__)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hello,world\n",
      "duration time: 1s\n",
      "hello\n"
     ]
    }
   ],
   "source": [
    "from functools import wraps\n",
    "\n",
    "def timeit(func):\n",
    "    @wraps(func)\n",
    "    def inner():\n",
    "        start = time.time()\n",
    "        func()\n",
    "        end = time.time()\n",
    "        print('duration time: %ds' % int(end - start))\n",
    "    return inner\n",
    "\n",
    "@timeit\n",
    "def hello():\n",
    "    time.sleep(1)\n",
    "    print('hello,world')\n",
    "\n",
    "# @timeit <==> hello=timeit(hello)\n",
    "hello()\n",
    "print(hello.__name__)"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 带参数的装饰器"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hello pangyazhou\n",
      "xxx: duration time: 1s\n"
     ]
    }
   ],
   "source": [
    "def timeit(prefix):  # 装饰器可传入参数\n",
    "    def decorator(func): # 多一层方法嵌套\n",
    "        @wraps(func)\n",
    "        def wrapper(*args, **kwargs):\n",
    "            start = time.time()\n",
    "            func(*args, **kwargs)\n",
    "            end = time.time()\n",
    "            print('%s: duration time: %ds' % (prefix, int(end - start)))\n",
    "        return wrapper\n",
    "    return decorator\n",
    "\n",
    "@timeit('xxx')\n",
    "def hello(name):\n",
    "    time.sleep(1)\n",
    "    print('hello %s' % name)\n",
    "\n",
    "hello('pangyazhou')"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 类实现装饰器"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hello pangyazhou\n",
      "xxx: duration time: 1s\n"
     ]
    }
   ],
   "source": [
    "class Timeit(object):\n",
    "    def __init__(self, prefix):\n",
    "        self.prefix = prefix\n",
    "\n",
    "    def __call__(self, func):\n",
    "        @wraps(func)\n",
    "        def wrapper(*args, **kwargs):\n",
    "            start = time.time()\n",
    "            func(*args, **kwargs)\n",
    "            end = time.time()\n",
    "            print('%s: duration time: %ds' % (self.prefix, int(end - start)))\n",
    "        return wrapper\n",
    "\n",
    "@timeit('xxx')\n",
    "def hello(name):\n",
    "    time.sleep(1)\n",
    "    print('hello %s' % name)\n",
    "\n",
    "hello('pangyazhou')"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 装饰器使用场景"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 记录调用日志"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hello pangyazhou\n"
     ]
    }
   ],
   "source": [
    "import logging\n",
    "\n",
    "logging.basicConfig(level=logging.INFO)\n",
    "def log(func):\n",
    "    @wraps(func)\n",
    "    def wrapper(*args, **kwargs):\n",
    "        logging.info('call method: %s %s %s', func.__name__, args, kwargs)\n",
    "        return func(*args, **kwargs)\n",
    "    return wrapper\n",
    "\n",
    "@log\n",
    "def hello(name):\n",
    "    time.sleep(1)\n",
    "    print('hello %s' % name)\n",
    "\n",
    "hello('pangyazhou')"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 记录方法耗时"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "hello,world\n",
      "duration time: 1s\n"
     ]
    }
   ],
   "source": [
    "def timeit(func):\n",
    "    @wraps(func)\n",
    "    def wrapper(*args, **kwargs):\n",
    "        start = time.time()\n",
    "        result = func(*args, **kwargs)\n",
    "        end = time.time()\n",
    "        print('duration time: %ds' % int(end - start))\n",
    "        return result\n",
    "    return wrapper\n",
    "\n",
    "@timeit\n",
    "def hello():\n",
    "    time.sleep(1)\n",
    "    print('hello,world')\n",
    "\n",
    "hello()"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "### 路由映射"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%% md\n"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "this is page1\n",
      "this is page2\n"
     ]
    }
   ],
   "source": [
    "class Router(object):\n",
    "    def __init__(self):\n",
    "        self.url_map = {}\n",
    "\n",
    "    def register(self, url):\n",
    "        def wrapper(func):\n",
    "            self.url_map[url] = func\n",
    "        return wrapper\n",
    "\n",
    "    def call(self, url):\n",
    "        func = self.url_map.get(url)\n",
    "        if not func:\n",
    "            raise ValueError('No url function %s', url)\n",
    "        return func()\n",
    "\n",
    "router = Router()\n",
    "\n",
    "@router.register(\"page1\")\n",
    "def page1():\n",
    "    return 'this is page1'\n",
    "\n",
    "@router.register('page2')\n",
    "def page2():\n",
    "    return 'this is page2'\n",
    "\n",
    "print(router.call('page1'))\n",
    "print(router.call('page2'))"
   ],
   "metadata": {
    "collapsed": false,
    "pycharm": {
     "name": "#%%\n"
    }
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
